/****************************************************************************
 * Copyright (C) 2009-2010 SciTouch LLC
 * 
 * This file is part of Indigo toolkit.
 * 
 * This file may be distributed and/or modified under the terms of the
 * GNU General Public License version 3 as published by the Free Software
 * Foundation and appearing in the file LICENSE.GPL included in the
 * packaging of this file.
 * 
 * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
 * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 ***************************************************************************/

#include "molecule/molecule_cis_trans.h"
#include "molecule/molecule_exact_matcher.h"
#include "molecule/molecule.h"
#include "molecule/molecule_stereocenters.h"
#include "molecule/molecule_substructure_matcher.h"
#include "graph/graph_affine_matcher.h"
#include "base_cpp/scanner.h"
#include "graph/filter.h"

MoleculeExactMatcher::MoleculeExactMatcher (const Molecule &query, const Molecule &target) :
_query(query),
_target(target),
_ee(target)
{
   flags = 0;
   rms_threshold = 0;

   _ee.cb_match_vertex = _matchAtoms;
   _ee.cb_match_edge = _matchBonds;
   _ee.cb_embedding = _embedding;

   _ee.userdata = this;

   _ee.setSubgraph(query);
}

bool MoleculeExactMatcher::find ()
{
   int i;

   if ((flags & CONDITION_3D) && !_query.have_xyz)
      throw Error("cannot do 3D match without XYZ in the query");

   for (i = _query.vertexBegin(); i != _query.vertexEnd(); i = _query.vertexNext(i))
   {
      const Atom &atom = _query.getAtom(i);
      const Vertex &vertex = _query.getVertex(i);

      if (atom.label == ELEM_H && vertex.degree() == 1 &&
          _query.getAtom(vertex.neiVertex(vertex.neiBegin())).label != ELEM_H)
         if (atom.isotope == 0 || !(flags & CONDITION_ISOTOPE))
            _ee.ignoreSubgraphVertex(i);
   }

   for (i = _target.vertexBegin(); i != _target.vertexEnd(); i = _target.vertexNext(i))
   {
      const Atom &atom = _target.getAtom(i);
      const Vertex &vertex = _target.getVertex(i);

      if (atom.label == ELEM_H && vertex.degree() == 1 &&
          _target.getAtom(vertex.neiVertex(vertex.neiBegin())).label != ELEM_H)
         if (atom.isotope == 0 || !(flags & CONDITION_ISOTOPE))
            _ee.ignoreSupergraphVertex(i);
   }
   
   if (flags & CONDITION_FRAGMENTS)
   {
      // Basic check: number of edges and vertices must be the same
      if (_ee.countUnmappedSubgraphVertices() != _ee.countUnmappedSupergraphVertices())
         return false;

      if (_ee.countUnmappedSubgraphEdges() != _ee.countUnmappedSupergraphEdges())
         return false;
   }
   else
   {
      _collectConnectedComponentsInfo();

      // Basic check: the query must contain no more fragments than the target
      int query_components = _query_decomposer->getComponentsCount();
      int target_components = _target_decomposer->getComponentsCount();

      if (query_components > target_components)
         return false;
   }

   if (_ee.process() == 0)
      return true;

   return false;
}

void MoleculeExactMatcher::_collectConnectedComponentsInfo ()
{
   // Target vertices filter initialization
   Filter target_vertices_filter;
   target_vertices_filter.init(_ee.getSupergraphMapping(), 
      Filter::NEQ, EmbeddingEnumerator::IGNORE);

   // Target decomposition
   _target_decomposer.create(_target);
   _target_decomposer->need_component_size = true;
   _target_decomposer->decompose(&target_vertices_filter);

   // Query vertices filter initialization
   Filter query_vertices_filter;
   query_vertices_filter.init(_ee.getSubgraphMapping(), 
      Filter::NEQ, EmbeddingEnumerator::IGNORE);

   // Query decomposition
   _query_decomposer.create(_query);
   _query_decomposer->need_component_size = true;
   _query_decomposer->decompose(&query_vertices_filter);
}

bool MoleculeExactMatcher::_matchAtoms (const Graph &subgraph, const Graph &supergraph,
                                       const int *core_sub, int sub_idx, int super_idx, void *userdata)
{
   const Molecule &query = (const Molecule &)subgraph;
   const Molecule &target = (const Molecule &)supergraph;

   const MoleculeExactMatcher *self = (const MoleculeExactMatcher *)userdata;
   int flags = self->flags;

   if (!(flags & CONDITION_FRAGMENTS))
   {
      const GraphDecomposer &target_decomposer = self->_target_decomposer.ref();
      const GraphDecomposer &query_decomposer = self->_query_decomposer.ref();

      int super_component = target_decomposer.getComponent(super_idx);
      int sub_component = query_decomposer.getComponent(sub_idx);

      int super_vertices = target_decomposer.getComponentVerticesCount(super_component);
      int sub_vertices = query_decomposer.getComponentVerticesCount(sub_component);
      if (super_vertices != sub_vertices)
         return false;

      int super_edges = target_decomposer.getComponentEdgesCount(super_component);
      int sub_edges = query_decomposer.getComponentEdgesCount(sub_component);
      if (super_edges != sub_edges)
         return false;
   }

   return matchAtoms(query, target, sub_idx, super_idx, flags);
}

bool MoleculeExactMatcher::_matchBonds (const Graph &subgraph, const Graph &supergraph,
                                       int sub_idx, int super_idx, void *userdata)
{
   const Molecule &query = (const Molecule &)subgraph;
   const Molecule &molecule = (const Molecule &)supergraph;

   const MoleculeExactMatcher *self = (const MoleculeExactMatcher *)userdata;
   int flags = self->flags;

   return matchBonds(query, molecule, sub_idx, super_idx, flags);
}

int MoleculeExactMatcher::_embedding (const Graph &subgraph, const Graph &supergraph,
                                     int *core_sub, int *core_super, void *userdata)
{
   MoleculeExactMatcher *self = (MoleculeExactMatcher *)userdata;
   const Molecule &query = (const Molecule &)subgraph;
   const Molecule &target = (const Molecule &)supergraph;

   if (self->flags & CONDITION_STEREO)
   {
      MoleculeStereocenters &qs = query.getStereocenters();
      MoleculeStereocenters &ts = target.getStereocenters();

      if (!MoleculeStereocenters::checkSub(qs, ts, core_sub, !(self->flags & CONDITION_ISOTOPE)))
         return 1;
      if (!MoleculeStereocenters::checkSub(ts, qs, core_super, !(self->flags & CONDITION_ISOTOPE)))
         return 1;

      if (!MoleculeCisTrans::checkSub(query, target, core_sub))
         return 1;
   }

   if (self->flags & CONDITION_3D)
   {
      GraphAffineMatcher matcher(subgraph, supergraph, core_sub);

      matcher.cb_get_xyz = MoleculeSubstructureMatcher::getAtomPos;

      if (!matcher.match(self->rms_threshold))
         return 1;
   }

   return 0;
}

bool MoleculeExactMatcher::_MatchToken::compare (const char *text) const
{
   return strcasecmp(t_text, text) == 0 ? true : false;
}

void MoleculeExactMatcher::parseConditions (const char *params, int &flags, float &rms_threshold)
{
   if (params == 0)
      throw Error("zero pointer passed to parseConditions()");

   static const _MatchToken token_list[] = {
      {"NONE", CONDITION_NONE}, 
      {"ELE", CONDITION_ELECTRONS},
      {"MAS", CONDITION_ISOTOPE}, 
      {"STE", CONDITION_STEREO},
      {"FRA", CONDITION_FRAGMENTS},
      {"ALL", CONDITION_ALL}
   };

   flags = CONDITION_NONE;
   rms_threshold = 0;

   BufferScanner scanner(params);

   QS_DEF(Array<char>, word);

   while (!scanner.isEOF())
   {
      int i;

      scanner.skipSpace();
      scanner.readWord(word, 0);

      if (word.size() < 2)
         throw Error("internal error on token reading");

      for (i = 0; i < NELEM(token_list); i++)
      {
         if (token_list[i].compare(word.ptr()))
            flags |= token_list[i].t_flag;
         else if (word[0] == '-' && token_list[i].compare(word.ptr() + 1))
            flags &= ~token_list[i].t_flag;
         else
            continue;
         break;
      }
      if (i == NELEM(token_list))
      {
         BufferScanner scanner2(word.ptr());

         if (scanner2.tryReadFloat(rms_threshold))
         {
            flags |= CONDITION_3D;
            return;
         }

         throw Error("parseConditions(): unknown token %s", word.ptr());
      }
   }
}

bool MoleculeExactMatcher::matchAtoms (const Molecule& query, const Molecule& target, int sub_idx, int super_idx, int flags) 
{
   const Atom &super_atom = target.getAtom(super_idx);
   const Atom &sub_atom = query.getAtom(sub_idx);

   if (query.isPseudoAtom(sub_idx) && target.isPseudoAtom(super_idx))
   {
      if (strcmp(query.getPseudoAtom(sub_idx), target.getPseudoAtom(super_idx)) != 0)
         return false;
   }
   else if (!query.isPseudoAtom(sub_idx) && !target.isPseudoAtom(super_idx))
   {
      if (sub_atom.label != super_atom.label)
         return false;
   }
   else
      return false;

   if (flags & CONDITION_ISOTOPE)
      if (sub_atom.isotope != super_atom.isotope)
         return false;

   if (flags & CONDITION_ELECTRONS)
   {
      if (sub_atom.charge != super_atom.charge)
         return false;

      if (sub_atom.valence != super_atom.valence)
         return false;

      if (sub_atom.radical != super_atom.radical)
         return false;
   }

   if (flags & CONDITION_STEREO)
   {
      int qtype = query.getStereocenters().getType(sub_idx);

      if (qtype != target.getStereocenters().getType(super_idx))
         return false;
   }
   return true;
}

bool MoleculeExactMatcher::matchBonds (const Molecule& query, const Molecule& target, int sub_idx, int super_idx, int flags) 
{
   const Bond &super_bond = target.getBond(super_idx);
   const Bond &sub_bond = query.getBond(sub_idx);

   if (flags & CONDITION_ELECTRONS)
      if (sub_bond.type != super_bond.type)
         return false;

   return true;
}

bool MoleculeExactMatcher::needCoords ()
{
   return (flags & CONDITION_3D) != 0;
}
